﻿using System.Diagnostics;
using Baula.TextGen.Framework.Sections;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Baula.TextGen.Framework.Tests
{
    [TestClass]
    public class PartTemplateTests
    {
        [TestMethod]
        public void Placeholders_SinglePartTemplate_CanBeAccessedViaName()
        {
            const string templateContent = @"This is the main Part
' === This is:my part
And that's its content.";
            var documentTemplate = new DocumentTemplate(templateContent);
            var partTemplate = documentTemplate.PartTemplates["This is:my part"];

            Assert.IsNotNull(partTemplate);
        }

        [TestMethod]
        public void Placeholders_WithLiteralTemplate_FindsNoPlaceholder()
        {
            const string templateContent = @"This is the main Part
' === The single part
There is no placeholder
in that whole part.";

            var documentTemplate = new DocumentTemplate(templateContent);
            
            Assert.AreEqual(0, documentTemplate.PartTemplates["The single part"].Placeholders.Count);
        }

        [TestMethod]
        public void Placeholders_TemplateWithJustAPlaceholderSection_FindsNoPlaceholder()
        {
            const string templateContent = "' %Section:APlaceholderSectionNotAPlaceholder%";

            var documentTemplate = new DocumentTemplate(templateContent);

            Assert.AreEqual(0, documentTemplate.MainPartTemplate.Placeholders.Count);
        }

        [TestMethod]
        public void Placeholders_WithSinglePlaceholderTemplate_FindsOnePlaceholder()
        {
            const string templateContent = @"This is the main Part
' === The single part
There is this %GreatPlaceholder% here
in that whole part.";

            var documentTemplate = new DocumentTemplate(templateContent);

            Assert.AreEqual(1, documentTemplate.PartTemplates["The single part"].Placeholders.Count);
        }

        [TestMethod]
        public void Sections_WithLiteralPart_FindsJustOneContentSection()
        {
            var documentTemplate = new DocumentTemplate(
                @"This is the main part
' === A test part
With just one two-lined
content section");

            var partTemplate = documentTemplate.PartTemplates["A test part"];
            
            Assert.AreEqual(1, partTemplate.Sections.Count);
            Assert.IsInstanceOfType(partTemplate.Sections[0], typeof(ContentSection));
        }

        [TestMethod]
        public void Sections_WithLiteralPart_GivesContentSectionWithContentEqualToWholePart()
        {
            const string expectedSectionContent = @"With just one content section
over
several lines";
            var documentTemplate = new DocumentTemplate(
                @"This is the main part
' === A test part
With just one content section
over
several lines");

            var partTemplate = documentTemplate.PartTemplates["A test part"];

            Assert.AreEqual(expectedSectionContent, partTemplate.Sections[0].Content);
        }

        [TestMethod]
        public void Sections_WithPartHavingRelocationSectionAtTheEnd_GivesContentSectionWithPartContentWithoutRelocationSection()
        {
            const string expectedSectionContent = @"This is the first line of the content section
and this is a line as well";
            var documentTemplate = new DocumentTemplate(@"This is the main part
' === A test part
This is the first line of the content section
and this is a line as well
' @Section:Somewhere
This is part of the section");
Debug.WriteLine("After DocumentTempalte ctor");
            var partTemplate = documentTemplate.PartTemplates["A test part"];

            Assert.AreEqual(expectedSectionContent, partTemplate.Sections[0].Content);
        }

        [TestMethod]
        public void Sections_WithPartHavingRelocationSectionAtTheEnd_GivesRelocationSectionWithPartContentWithoutContentSection()
        {
            const string expectedSectionContent = @"This is part of the relocation section
and also this.";
            var documentTemplate = new DocumentTemplate(@"This is the main part
' === A test part
This is the content section
' @Section:Somewhere
This is part of the relocation section
and also this.");
            Debug.WriteLine("After DocumentTempalte ctor");
            var partTemplate = documentTemplate.PartTemplates["A test part"];

            Assert.AreEqual(expectedSectionContent, partTemplate.Sections[1].Content);
        }

        [TestMethod]
        public void Sections_WithPartHaving2RelocationSectionsAtTheEnd_Gives1stRelocationSectionWithPartContentWithoutTheOtherSections()
        {
            const string expectedSectionContent = @"This is part of the 1st relocation section
and also this.";
            var documentTemplate = new DocumentTemplate(@"This is the main part
' === A test part
This is the content section
' @Section:Somewhere
This is part of the 1st relocation section
and also this.
' @Section:ToWhereYouWant
Another line to relocate");

            var partTemplate = documentTemplate.PartTemplates["A test part"];

            Assert.AreEqual(expectedSectionContent, partTemplate.Sections[1].Content);
        }

        [TestMethod]
        public void Sections_WithPartHaving2RelocationSectionsAtTheEnd_Gives2ndRelocationSectionWithPartContentWithoutTheOtherSections()
        {
            const string expectedSectionContent = "Another line to relocate";
            var documentTemplate = new DocumentTemplate(@"This is the main part
' === A test part
This is the content section
' @Section:Somewhere
This is part of the 1st relocation section
and also this.
' @Section:ToWhereYouWant
Another line to relocate");

            var partTemplate = documentTemplate.PartTemplates["A test part"];

            Assert.AreEqual(expectedSectionContent, partTemplate.Sections[2].Content);
        }

        [TestMethod]
        public void Sections_WithPartHavingJustOneRelocationSection_GivesRelocationSectionWithPartContent()
        {
            const string expectedSectionContent = @"This is part of the relocation section
which is the single section herein.";
            var documentTemplate = new DocumentTemplate(@"This is the main part
' === A test part
' @Section:Somewhere
This is part of the relocation section
which is the single section herein.");
            var partTemplate = documentTemplate.PartTemplates["A test part"];

            var section = partTemplate.Sections[0];
            Assert.IsInstanceOfType(section, typeof(RelocationSection));
            Assert.AreEqual(expectedSectionContent, section.Content);
        }

        [TestMethod]
        public void Sections_WithPartHaving2RelocationSection_GivesThemWithProperContent()
        {
            const string expectedSectionContent1 = @"This is part of the 1st relocation section
which is not the single section anymore.";
            const string expectedSectionContent2 = @"The reason is that there is another
relocation section as well.";
            var documentTemplate = new DocumentTemplate(@"This is the main part
' === A test part
' @Section:Somewhere
This is part of the 1st relocation section
which is not the single section anymore.
' @Section:TheNew
The reason is that there is another
relocation section as well.");
            var partTemplate = documentTemplate.PartTemplates["A test part"];

            // SMELL four asserts (but it's more or less the same aspect)
            var section1 = partTemplate.Sections[0];
            Assert.IsInstanceOfType(section1, typeof(RelocationSection));
            Assert.AreEqual(expectedSectionContent1, section1.Content);
            var section2 = partTemplate.Sections[1];
            Assert.IsInstanceOfType(section2, typeof(RelocationSection));
            Assert.AreEqual(expectedSectionContent2, section2.Content);
        }

        [TestMethod]
        public void Sections_WithLiteralMainPart_GivesASingleContentSectionWithAllTheContent()
        {
            var documentTemplate = new DocumentTemplate(@"This is the main part's 1st line
this is the 2nd line
and this the 3rd and last line");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(1, sections.Count);
            var section = sections[0];
            Assert.IsInstanceOfType(section, typeof(ContentSection));
            Assert.AreEqual(documentTemplate.MainContent, section.Content);
        }

        [TestMethod]
        public void Sections_WithJustAPlaceholderSection_GivesASinglePlaceholderSection()
        {
            var documentTemplate = new DocumentTemplate(@"' %Section:MyPlaceholder%");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(1, sections.Count);
            var section = sections[0];
            Assert.IsInstanceOfType(section, typeof(PlaceholderSection));
        }

        [TestMethod]
        public void Sections_WithJustAPlaceholderSection_GivesThePlaceholderSectionWithTheGivenName()
        {
            var documentTemplate = new DocumentTemplate(@"' %Section:MyPlaceholder%");

            var sections = documentTemplate.MainPartTemplate.Sections;
            var placeholderSection = (PlaceholderSection) sections[0];
            Assert.AreEqual("MyPlaceholder", placeholderSection.Name);
        }

        [TestMethod]
        public void Sections_WithSeriesContentPlaceholder_GivesContentSectionAsFirstOne()
        {
            var documentTemplate = new DocumentTemplate(
                @"This is some regular text
forming the main part.
' %Section:APlaceholderSection%");

            Assert.IsInstanceOfType(documentTemplate.MainPartTemplate.Sections[0], typeof(ContentSection));
        }

        [TestMethod]
        public void Sections_WithSeriesContentPlaceholder_FindsAllSectionsWithAppropriateState()
        {
            var documentTemplate = new DocumentTemplate(
                @"This is some regular text
forming the main part.
' %Section:APlaceholderSection%");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(2, sections.Count);
            AssertionHelpers.AssertContentSection(sections[0], "This is some regular text\r\nforming the main part.");
            AssertionHelpers.AssertPlaceholderSection(sections[1], "APlaceholderSection");
        }

        [TestMethod]
        public void Sections_WithSeriesContentPlaceholderContent_FindsAllSectionsWithAppropriateState()
        {
            var documentTemplate = new DocumentTemplate(
                @"This is some regular text
forming the main part.
' %Section:APlaceholderSection%
Some other
content section");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(3, sections.Count);
            AssertionHelpers.AssertContentSection(sections[0], "This is some regular text\r\nforming the main part.");
            AssertionHelpers.AssertPlaceholderSection(sections[1], "APlaceholderSection");
            AssertionHelpers.AssertContentSection(sections[2], "Some other\r\ncontent section");
        }

        [TestMethod]
        public void Sections_WithSeriesContentPlaceholderContentPlaceholder_FindsAllSectionsWithAppropriateState()
        {
            var documentTemplate = new DocumentTemplate(
                @"This is some regular text
forming the main part.
' %Section:APlaceholderSection%
Some other
content section
' %Section:TheSecondPlaceholder%");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(4, sections.Count);
            AssertionHelpers.AssertContentSection(sections[0], "This is some regular text\r\nforming the main part.");
            AssertionHelpers.AssertPlaceholderSection(sections[1], "APlaceholderSection");
            AssertionHelpers.AssertContentSection(sections[2], "Some other\r\ncontent section");
            AssertionHelpers.AssertPlaceholderSection(sections[3], "TheSecondPlaceholder");
        }

        [TestMethod]
        public void Sections_WithSeriesContentPlaceholderContentPlaceholderContent_FindsAllSectionsWithAppropriateState()
        {
            var documentTemplate = new DocumentTemplate(
                @"This is some regular text
forming the main part.
' %Section:APlaceholderSection%
Some other
content section
' %Section:TheSecondPlaceholder%
The closing
content section is here");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(5, sections.Count);
            AssertionHelpers.AssertContentSection(sections[0], "This is some regular text\r\nforming the main part.");
            AssertionHelpers.AssertPlaceholderSection(sections[1], "APlaceholderSection");
            AssertionHelpers.AssertContentSection(sections[2], "Some other\r\ncontent section");
            AssertionHelpers.AssertPlaceholderSection(sections[3], "TheSecondPlaceholder");
            AssertionHelpers.AssertContentSection(sections[4], "The closing\r\ncontent section is here");
        }

        [TestMethod]
        public void Sections_WithSeriesPlaceholderContent_FindsAllSectionsWithAppropriateState()
        {
            var documentTemplate = new DocumentTemplate(
                @"' %Section:ThePlaceholderSection%
This is some regular text
forming the main part.");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(2, sections.Count);
            AssertionHelpers.AssertPlaceholderSection(sections[0], "ThePlaceholderSection");
            AssertionHelpers.AssertContentSection(sections[1], "This is some regular text\r\nforming the main part.");
        }

        [TestMethod]
        public void Sections_WithSeriesPlaceholderContentPlaceholder_FindsAllSectionsWithAppropriateState()
        {
            var documentTemplate = new DocumentTemplate(
                @"' %Section:APlaceholderSection%
This is some regular text
forming the main part.
' %Section:LastPlaceholder%");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(3, sections.Count);
            AssertionHelpers.AssertPlaceholderSection(sections[0], "APlaceholderSection");
            AssertionHelpers.AssertContentSection(sections[1], "This is some regular text\r\nforming the main part.");
            AssertionHelpers.AssertPlaceholderSection(sections[2], "LastPlaceholder");
        }

        [TestMethod]
        public void Sections_WithSeriesPlaceholderContentPlaceholderContent_FindsAllSectionsWithAppropriateState()
        {
            var documentTemplate = new DocumentTemplate(
                @"' %Section:APlaceholderSection%
This is some regular text
forming the main part.
' %Section:LastPlaceholder%
This is the closing
content section.
Yess! Yess!");

            var sections = documentTemplate.MainPartTemplate.Sections;
            Assert.AreEqual(4, sections.Count);
            AssertionHelpers.AssertPlaceholderSection(sections[0], "APlaceholderSection");
            AssertionHelpers.AssertContentSection(sections[1], "This is some regular text\r\nforming the main part.");
            AssertionHelpers.AssertPlaceholderSection(sections[2], "LastPlaceholder");
            AssertionHelpers.AssertContentSection(sections[3], "This is the closing\r\ncontent section.\r\nYess! Yess!");
        }

        [TestMethod]
        public void Sections_RelocationSectionWithIndentedToken_IsRecognized()
        {
            var documentTemplate =
                new DocumentTemplate(@"Main part
' === Some part
    ' @Section:SomeSection");
            var partTemplate = documentTemplate.PartTemplates["Some part"];
            var sections = partTemplate.Sections;

            Assert.AreEqual(1, sections.Count);
            Assert.IsInstanceOfType(sections[0], typeof(RelocationSection));
        }

        [TestMethod]
        public void Sections_RelocationSectionWithIndentedToken_RelocatesToCorrentPlaceholderSection()
        {
            var documentTemplate =
                new DocumentTemplate(@"Main part
' === Some part
    ' @Section:SomeSection");
            const string expectedPlaceholderSectionName = "SomeSection";

            var partTemplate = documentTemplate.PartTemplates["Some part"];
            var sections = partTemplate.Sections;

            Assert.AreEqual(1, sections.Count);
            var section = (RelocationSection)sections[0];
            Assert.AreEqual(expectedPlaceholderSectionName, section.TargetSectionName);
        }

        [TestMethod]
        [ExpectedException(typeof(RelocationSectionInvalidInMainPartException))]
        public void Sections_RelocationSectionInMainPartTemplate_ThrowsException()
        {
            const string templateContent = @"Main part
' @Section:Something
A relocation section may not occur within the main part of a document template.";

            new DocumentTemplate(templateContent);  // Everything interesting happens here.
        }

        [TestMethod]
        [ExpectedException(typeof(PlaceholderSectionInvalidOutsideMainPartException))]
        public void Sections_PlaceholderSectionInNonMainPartPart_ThrowsException()
        {
            const string templateContent = @"Main part
' === My part
' %Section:InvalidPlaceholderSectionHere%";

            new DocumentTemplate(templateContent);  // Everything interesting happens here.
        }

        [TestMethod]
        public void CreateFromContent_WithJustLiteralText_ResultsInASingleContentSectionWithoutPlaceholders()
        {
            const string partTemplateContent = @"This is just literal text
spreading two ...
... no: three! lines.";
            var partTemplate = PartTemplate.CreateFromContent(partTemplateContent);

            Assert.AreEqual(1, partTemplate.Sections.Count);
            Assert.IsInstanceOfType(partTemplate.Sections[0], typeof(ContentSection));
            var contentSection = (ContentSection)partTemplate.Sections[0];
            Assert.AreEqual(partTemplateContent, contentSection.Content);
            Assert.AreEqual(0, partTemplate.Placeholders.Count);
        }

        [TestMethod]
        public void CreateFromContent_WithPartHeadingOnFirstLine_ResultsInAProperlyNamedPartTemplate()
        {
            const string partTemplateContent = @"' === The Part
With some irrelevant content";
            var partTemplate = PartTemplate.CreateFromContent(partTemplateContent);

            Assert.AreEqual("The Part", partTemplate.Name);
        }

        [TestMethod]
        public void CreateFromContent_WithPartHeadingOnFirstLine_ResultsInASingleContentSectionWithoutPlaceholders()
        {
            const string content = @"Some irrelevant content";
            const string partTemplateContent = "' === The Part\r\n" + content;
            var partTemplate = PartTemplate.CreateFromContent(partTemplateContent);

            Assert.AreEqual(1, partTemplate.Sections.Count);
            Assert.IsInstanceOfType(partTemplate.Sections[0], typeof(ContentSection));
            var contentSection = (ContentSection)partTemplate.Sections[0];
            Assert.AreEqual(content, contentSection.Content);
            Assert.AreEqual(0, partTemplate.Placeholders.Count);
        }

        [TestMethod]
        [ExpectedException(typeof(SubPartsNotSupportedException))]
        public void CreateFromContent_WithPartHeadingOnSecondLine_ThrowsException()
        {
            const string partTemplateContent = @" Some normal text
' === Another part
Subparts are not supported.";
            PartTemplate.CreateFromContent(partTemplateContent);  // Exception occurs in constructor
        }

        [TestMethod]
        [ExpectedException(typeof(SubPartsNotSupportedException))]
        public void CreateFromContent_WithPartHeadingOnFirstAndSecondLine_ThrowsException()
        {
            const string partTemplateContent = @"' === The part itself (what's fine)
' === And another part
Subparts are not supported.";
            PartTemplate.CreateFromContent(partTemplateContent);  // Exception occurs in constructor
        }

        [TestMethod]
        public void CreateFromContent_ContainingPlaceholder_FindsThatPlaceholder()
        {
            const string partTemplateContent = "This is %ThePlaceholder%.";

            var partTemplate = PartTemplate.CreateFromContent(partTemplateContent);

            Assert.AreEqual(1, partTemplate.Placeholders.Count);
            Assert.AreEqual("ThePlaceholder", partTemplate.Placeholders[0]);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidPartHeadingLineException))]
        public void Ctor_WithNonMatchingHeadingLine_ThrowsException()
        {
            new PartTemplate("This is not a proper heading line for a part template."); // Exception occurs in constructor
        }
    }
}
